/*
 * $Id: TreeTableCellEditor.java,v 1.1.1.1 2003/09/01 05:13:44 bsletten Exp $
 */
package com.idea.common.swing.treetable;

import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;

import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;

import com.idea.common.swing.treetable.JTreeTable.TreeTableTextField;

/**
 * An editor that can be used to edit the tree column. This extends
 * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField) to
 * perform the actual editing.
 * <p>
 * To support editing of the tree column we can not make the tree editable. The
 * reason this doesn't work is that you can not use the same component for
 * editing and renderering. The table may have the need to paint cells, while a
 * cell is being edited. If the same component were used for the rendering and
 * editing the component would be moved around, and the contents would change.
 * When editing, this is undesirable, the contents of the text field must stay
 * the same, including the caret blinking, and selections persisting. For this
 * reason the editing is done via a TableCellEditor.
 * <p>
 * Another interesting thing to be aware of is how tree positions its render and
 * editor. The render/editor is responsible for drawing the icon indicating the
 * type of node (leaf, branch...). The tree is responsible for drawing any other
 * indicators, perhaps an additional +/- sign, or lines connecting the various
 * nodes. So, the renderer is positioned based on depth. On the other hand,
 * table always makes its editor fill the contents of the cell. To get the
 * allusion that the table cell editor is part of the tree, we don't want the
 * table cell editor to fill the cell bounds. We want it to be placed in the
 * same manner as tree places it editor, and have table message the tree to
 * paint any decorations the tree wants. Then, we would only have to worry about
 * the editing part. The approach taken here is to determine where tree would
 * place the editor, and to override the <code>reshape</code> method in the
 * JTextField component to nudge the textfield to the location tree would place
 * it. Since JTreeTable will paint the tree behind the editor everything should
 * just work. So, that is what we are doing here. Determining of the icon
 * position will only work if the TreeCellRenderer is an instance of
 * DefaultTreeCellRenderer. If you need custom TreeCellRenderers, that don't
 * descend from DefaultTreeCellRenderer, and you want to support editing in
 * JTreeTable, you will have to do something similiar.
 */
public class TreeTableCellEditor extends DefaultCellEditor {
	private final JTreeTable table;

	public TreeTableCellEditor(JTreeTable table) {
		super(new TreeTableTextField());
		this.table = table;

	}

	/**
	 * Overridden to determine an offset that tree would place the editor at.
	 * The offset is determined from the <code>getRowBounds</code> JTree
	 * method, and additionally from the icon DefaultTreeCellRenderer will use.
	 * <p>
	 * The offset is then set on the TreeTableTextField component created in the
	 * constructor, and returned.
	 */
	public Component getTableCellEditorComponent(JTable table, Object value,
			boolean isSelected, int r, int c) {
		Component component = super.getTableCellEditorComponent(table, value,
				isSelected, r, c);
		JTree t = this.table.getTree();
		boolean rv = t.isRootVisible();
		int offsetRow = rv ? r : r - 1;
		Rectangle bounds = t.getRowBounds(offsetRow);
		int offset = bounds.x;
		TreeCellRenderer tcr = t.getCellRenderer();
		if (tcr instanceof DefaultTreeCellRenderer) {
			Object node = t.getPathForRow(offsetRow).getLastPathComponent();
			Icon icon;
			if (t.getModel().isLeaf(node))
				icon = ((DefaultTreeCellRenderer) tcr).getLeafIcon();
			else if (this.table.tree.isExpanded(offsetRow))
				icon = ((DefaultTreeCellRenderer) tcr).getOpenIcon();
			else
				icon = ((DefaultTreeCellRenderer) tcr).getClosedIcon();
			if (icon != null) {
				offset += ((DefaultTreeCellRenderer) tcr).getIconTextGap()
						+ icon.getIconWidth();
			}
		}
		((TreeTableTextField) getComponent()).offset = offset;

		return component;
	}

	/**
	 * This is overridden to forward the event to the tree. This will return
	 * true if the click count >= 3, or the event is null.
	 */
	public boolean isCellEditable(EventObject e) {
		if (e instanceof MouseEvent) {
			MouseEvent me = (MouseEvent) e;
			// If the modifiers are not 0 (or the left mouse button),
			// tree may try and toggle the selection, and table
			// will then try and toggle, resulting in the
			// selection remaining the same. To avoid this, we
			// only dispatch when the modifiers are 0 (or the left mouse
			// button).
			if (me.getModifiers() == 0
					|| me.getModifiers() == InputEvent.BUTTON1_MASK) {
				for (int counter = table.getColumnCount() - 1; counter >= 0; counter--) {
					if (table.getColumnClass(counter) == TreeTableModel.class) {
						MouseEvent newME = new MouseEvent(this.table.tree, me
								.getID(), me.getWhen(), me.getModifiers(), me
								.getX()
								- table.getCellRect(0, counter, true).x, me
								.getY(), me.getClickCount(), me
								.isPopupTrigger());
						this.table.tree.dispatchEvent(newME);
						break;
					}
				}
			}
			if (me.getClickCount() >= 3) {
				return true;
			}
			return false;
		}
		if (e == null) {
			return true;
		}
		return false;
	}
}